package com.idea.relax.tool.core;

/**
 * @author: aron
 * @date: 2023/5/22
 * @description: 坐标转换工具类，用于不同坐标系之间的转换
 */
public class CoordsTransformUtil {
	public static final double PI = 3.141592653589793D;
	public static final double A = 6378000.0D;
	public static final double EE = 0.006693421622965943D;

	/**
	 * 将 GPS 坐标（WGS84）转换为国测局坐标（GCJ02）
	 *
	 * @param lon 经度
	 * @param lat 纬度
	 * @return 转换后的坐标 [经度, 纬度]
	 */
	public static double[] gps84ToGcj02(double lon, double lat) {
		return getDoubles(lon, lat);
	}

	/**
	 * 将国测局坐标（GCJ02）转换为 GPS 坐标（WGS84）
	 *
	 * @param lon 经度
	 * @param lat 纬度
	 * @return 转换后的坐标 [经度, 纬度]
	 */
	public static double[] gcjToGps84(double lon, double lat) {
		double[] gps = transform(lon, lat);
		double lontitude = lon * 2.0D - gps[0];
		double latitude = lat * 2.0D - gps[1];
		return new double[]{lontitude, latitude};
	}

	/**
	 * 将国测局坐标（GCJ02）转换为百度坐标（BD09）
	 *
	 * @param ggLon 经度
	 * @param ggLat 纬度
	 * @return 转换后的坐标 [经度, 纬度]
	 */
	public static double[] gcj02ToBd09(double ggLon, double ggLat) {
		double z = Math.sqrt(ggLon * ggLon + ggLat * ggLat) + 2.0E-5D * Math.sin(ggLat * Math.PI);
		double theta = Math.atan2(ggLat, ggLon) + 3.0E-6D * Math.cos(ggLon * Math.PI);
		double bdLon = z * Math.cos(theta) + 0.0065D;
		double bdLat = z * Math.sin(theta) + 0.006D;
		return new double[]{bdLon, bdLat};
	}

	/**
	 * 将百度坐标（BD09）转换为国测局坐标（GCJ02）
	 *
	 * @param bdLon 经度
	 * @param bdLat 纬度
	 * @return 转换后的坐标 [经度, 纬度]
	 */
	public static double[] bd09ToGcj02(double bdLon, double bdLat) {
		double x = bdLon - 0.0065D;
		double y = bdLat - 0.006D;
		double z = Math.sqrt(x * x + y * y) - 2.0E-5D * Math.sin(y * Math.PI);
		double theta = Math.atan2(y, x) - 3.0E-6D * Math.cos(x * Math.PI);
		double ggLon = z * Math.cos(theta);
		double ggLat = z * Math.sin(theta);
		return new double[]{ggLon, ggLat};
	}

	/**
	 * 将百度坐标（BD09）转换为 GPS 坐标（WGS84）
	 *
	 * @param bdLon 经度
	 * @param bdLat 纬度
	 * @return 转换后的坐标 [经度, 纬度]
	 */
	public static double[] bd09ToGps84(double bdLon, double bdLat) {
		double[] gcj02 = bd09ToGcj02(bdLon, bdLat);
		return gcjToGps84(gcj02[0], gcj02[1]);
	}

	/**
	 * 判断坐标是否在中国境内
	 *
	 * @param lon 经度
	 * @param lat 纬度
	 * @return true表示在中国境内，false表示不在中国境内
	 */
	public static boolean outOfChina(double lon, double lat) {
		return (lon < 73.6D || lon > 135.1D) || (lat < 3.9D || lat > 53.5D);
	}

	/**
	 * 坐标转换
	 *
	 * @param lon 经度
	 * @param lat 纬度
	 * @return 转换后的坐标 [经度, 纬度]
	 */
	public static double[] transform(double lon, double lat) {
		return getDoubles(lon, lat);
	}

	private static double[] getDoubles(double lon, double lat) {
		if (outOfChina(lon, lat)) {
			return new double[]{lon, lat};
		}
		double dLat = transformLat(lon - 105.0D, lat - 35.0D);
		double dLon = transformLon(lon - 105.0D, lat - 35.0D);
		double radLat = lat / 180.0D * Math.PI;
		double magic = Math.sin(radLat);
		magic = 1.0D - 0.006693421622965943D * magic * magic;
		double sqrtMagic = Math.sqrt(magic);
		dLat = dLat * 180.0D / 6335309.356888724D / magic * sqrtMagic * Math.PI;
		dLon = dLon * 180.0D / 6378000.0D / sqrtMagic * Math.cos(radLat) * Math.PI;
		double mgLat = lat + dLat;
		double mgLon = lon + dLon;
		return new double[]{mgLon, mgLat};
	}

	private static double transformLat(double x, double y) {
		double ret = -100.0D + 2.0D * x + 3.0D * y + 0.2D * y * y + 0.1D * x * y + 0.2D * Math.sqrt(Math.abs(x));
		ret += (20.0D * Math.sin(6.0D * x * Math.PI) + 20.0D * Math.sin(2.0D * x * Math.PI)) * 2.0D / 3.0D;
		ret += (20.0D * Math.sin(y * Math.PI) + 40.0D * Math.sin(y / 3.0D * Math.PI)) * 2.0D / 3.0D;
		ret += (160.0D * Math.sin(y / 12.0D * Math.PI) + 320.0D * Math.sin(y * Math.PI / 30.0D)) * 2.0D / 3.0D;
		return ret;
	}

	private static double transformLon(double x, double y) {
		double ret = 300.0D + x + 2.0D * y + 0.1D * x * x + 0.1D * x * y + 0.1D * Math.sqrt(Math.abs(x));
		ret += (20.0D * Math.sin(6.0D * x * Math.PI) + 20.0D * Math.sin(2.0D * x * Math.PI)) * 2.0D / 3.0D;
		ret += (20.0D * Math.sin(x * Math.PI) + 40.0D * Math.sin(x / 3.0D * Math.PI)) * 2.0D / 3.0D;
		ret += (150.0D * Math.sin(x / 12.0D * Math.PI) + 300.0D * Math.sin(x / 30.0D * Math.PI)) * 2.0D / 3.0D;
		return ret;
	}
}
